/*
用于ActionScript 3.0的CASA Lib
版权所有（c）2011，Aaron Clinger和CASA Lib的贡献者
版权所有。

重新分配和使用源和二进制形式，有或没有
修改，允许，条件是满足以下条件：

- 源代码的再分发必须保留上述版权声明，
此条件列表和以下免责声明。

- 二进制形式的再分发必须复制上述版权声明，
此条件列表和文档中的以下免责声明
和/或提供有该分布的其他材料。

- 无论是CASA Lib的名称还是其贡献者的名称
可用于支持或促进从该软件派生的产品
无需事先书面许可。

本软件由版权所有者和贡献者“按原样”
以及任何明示或默示的保证，包括但不限于
对适销性和特定用途的适用性的暗示保证
免责声明。在任何情况下，版权所有者或贡献者
对任何直接，间接，偶发，特殊，惩罚或任何责任
相关损害（包括但不限于采购）
替代产品或服务;丢失使用，数据或利润;或业务
中断），无论是由于任何责任理论，无论何种情况
合同，严格责任或侵权（包括疏忽或其他）
以任何方式使用本软件，即使已被告知
这种损害的可能性。
*/
package com.asframe.utils
{
	import flash.xml.XMLDocument;
	import flash.xml.XMLNodeType;
	import flash.xml.XMLNode;


	/**
		用于操作和搜索字符串的实用程序。

		@author Aaron Clinger
		@author Mike Creighton
		@author David Nelson
		@author Jon Adams
		@version 06/23/11
	*/
	public class StringUtil
	{
		/**< Whitespace characters (space, tab, new line and return). */
		public static const WHITESPACE:String = " \t\n\r";
		/** <与{@link #toTitleCase}配合使用的小/短字的默认列表。 */
		public static var SMALL_WORDS:Array   = new Array("a", "an", "and", "as", "at", "but", "by", "en", "for", "if", "is", "in", "of", "on", "or", "the", "to", "v", "via", "vs");


		/**
			确定在给定数字的情况下是否应使用名词的单数或复数形式。

			@param count：项目数。
			@param singular：单词的单数版本。
			@param plural：这个词的复数版本。 如果没有定义单词，方法将“s”附加到单数单词。
			@return单词的复数形式。
			@example
				<code>
					trace(StringUtil.pluralize(1, "Dog")); // Traces "Dog"
					trace(StringUtil.pluralize(3, "Dog")); // Traces "Dogs"
					trace(StringUtil.pluralize(2, "Child", "Children")); // Traces "Children"
				</code>
		*/
		public static function pluralize(count:int, singular:String, plural:String = null):String {
			plural ||= singular + 's';

			return Math.abs(count) == 1 ? singular : plural;
		}

		/**
			返回缩短的字符串。
			@param source：要缩短的字符串。
			@param trailing：要从字符串的结尾删除的字符数。
			@param leading：要从字符串开始删除的字符数。
			@param separator：分隔字符串的开始和结束的字符。
			@return缩短的字符串。
			@example
				<code>
					trace(StringUtil.truncate('Mississippi', 2, 3, '...')); // Traces "Mis...pi"
				</code>
		*/
		public static function truncate(source:String, trailing:uint, leading:uint = 0, separator:String = ""):String {
			const lead:String  = source.substr(0, leading);
			const trail:String = source.substr(-trailing, trailing);

			return lead + separator + trail;
		}


		/**
			将源字符串转换为字幕大小写。

			@param source：返回作为标题的字符串。
			@param lowerCaseSmallWords：表示使{@link #SMALL_WORDS小字}小写字母<code> true </ code>或大写小字<code> false </ code>。
			@return带有大写字母的字符串。
		*/
		public static function toTitleCase(source:String, lowerCaseSmallWords:Boolean = true):String {
			source = StringUtil._checkWords(source.toLowerCase(), ' ', true, lowerCaseSmallWords);

			var parts:Array = source.split(' ');
			var last:int    = parts.length - 1;

			if (!StringUtil._isIgnoredWord(parts[0]))
				parts[0] = StringUtil._capitalizeFirstLetter(parts[0]);

			if (!StringUtil._isIgnoredWord(parts[last]) && (!lowerCaseSmallWords || !StringUtil._isSmallWord(parts[last])))
				parts[last] = StringUtil._capitalizeFirstLetter(parts[last]);

			source = parts.join(' ');

			if (StringUtil.contains(source, ': ')) {
				var i:int = -1;
				parts     = source.split(': ');

				while (++i < parts.length)
					parts[i] = StringUtil._capitalizeFirstLetter(parts[i]);

				source = parts.join(': ');
			}

			return source;
		}

		protected static function _checkWords(source:String, delimiter:String, checkForDashes:Boolean = false, lowerCaseSmallWords:Boolean = false):String {
			var words:Array = source.split(delimiter);
			var l:int       = words.length;
			var word:String;

			while (l--) {
				word = words[l];

				words[l] = StringUtil._checkWord(word, checkForDashes, lowerCaseSmallWords);
			}

			return words.join(delimiter);
		}

		protected static function _checkWord(word:String, checkForDashes:Boolean, lowerCaseSmallWords:Boolean):String {
			if (StringUtil._isIgnoredWord(word))
				return word;

			if (lowerCaseSmallWords)
				if (StringUtil._isSmallWord(word))
					return word.toLowerCase();

			if (checkForDashes) {
				var dashes:Array = new Array('-', '–', '—');
				var i:int        = -1;
				var dashFound:Boolean;

				while (++i < dashes.length) {
					if (StringUtil.contains(word, dashes[i]) != 0) {
						word = StringUtil._checkWords(word, dashes[i], false, true);
						dashFound = true;
					}
				}

				if (dashFound)
					return word;
			}

			return StringUtil._capitalizeFirstLetter(word);
		}

		protected static function _isIgnoredWord(word:String):Boolean {
			var periodIndex:int = word.indexOf('.');
			var upperIndex:int  = StringUtil.indexOfUpperCase(word);

			if (periodIndex != -1 && periodIndex != word.length - 1 || upperIndex != -1 && upperIndex != 0)
				return true;

			return false;
		}

		protected static function _isSmallWord(word:String):Boolean {
			return StringUtil.SMALL_WORDS.indexOf(StringUtil.getLettersFromString(word).toLowerCase()) > -1;
		}

		protected static function _capitalizeFirstLetter(source:String):String {
			var i:int = -1;
			while (++i < source.length)
				if (!StringUtil.isPunctuation(source.charAt(i)))
					return StringUtil.replaceAt(source, i, source.charAt(i).toUpperCase());

			return source;
		}

		/**
			创建“通用唯一”标识符（RFC 4122，版本4）。

			@return返回UUID。
		*/
		public static function uuid():String {
			const specialChars:Array = new Array('8', '9', 'A', 'B');

			return StringUtil.createRandomIdentifier(8, 15) + '-' + StringUtil.createRandomIdentifier(4, 15) + '-4' + StringUtil.createRandomIdentifier(3, 15) + '-' + specialChars[NumberUtil.randomIntegerWithinRange(0, 3)] + StringUtil.createRandomIdentifier(3, 15) + '-' + StringUtil.createRandomIdentifier(12, 15);
		}

		/**
			创建指定长度和复杂性的随机标识符。

			@param length：随机标识符的字符长度。
			@param radix：每个字符的唯一/允许值的数量（61是最大复杂度）。
			@return返回一个随机标识符。
			@usageNote对于大小为35的最大<code> radix </ code>中的不区分大小写的标识符，对于数字标识符，最大为<code> radix </ code>为9。
		*/
		public static function createRandomIdentifier(length:uint, radix:uint = 61):String {
			const characters:Array = new Array('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z');
			const id:Array         = new Array();
			radix                  = (radix > 61) ? 61 : radix;

			while (length--) {
				id.push(characters[NumberUtil.randomIntegerWithinRange(0, radix)]);
			}

			return id.join('');
		}

		/**
			Detects URLs in a String and wraps them in a link.

			@param source: String in which to automatically wrap links around URLs.
			@param window: The browser window or HTML frame in which to display the URL.
			@param className: An optional CSS class name to add to the link. You can specify multiple classes by seperating the class names with spaces.
			@return Returns the String with any URLs wrapped in a link.
			//todo see中有url不能生成asdoc
			#see <a href="http://daringfireball.net/2010/07/improved_regex_for_matching_urls">Read more about the regular expression used by this method.</a>
		*/
		public static function autoLink(source:String, window:String = "_blank", className:String = null):String {
			const pattern:RegExp = new RegExp('(?i)\\b((?:[a-z][\\w-]+:(?:/{1,3}|[a-z0-9%])|www\\d{0,3}[.]|[a-z0-9.\\-]+[.][a-z]{2,4}/)(?:[^\\s()<>]+|\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\))+(?:\\(([^\\s()<>]+|(\\([^\\s()<>]+\\)))*\\)|[^\\s`!()\\[\\]{};:\'".,<>?«»“”‘’]))', 'g')
			className            = (className != "" && className != null) ? ' class="' + className + '"' : '';
			window               = (window != null) ? ' target="' + window + '"' : '';

			return source.replace(pattern, '<a href="$1"' + window + className + '>$1</a>');
		}

		/**
			Converts all applicable characters to HTML entities.

			@param source: String to convert.
			@return Returns the converted string.
		*/
		public static function htmlEncode(source:String):String {
			return new XML(new XMLNode(XMLNodeType.TEXT_NODE, source)).toXMLString();
		}

		/**
			Converts all HTML entities to their applicable characters.

			@param source: String to convert.
			@return Returns the converted string.
		*/
		public static function htmlDecode(source:String):String {
			return new XMLDocument(source).firstChild.nodeValue;
		}

		/**
			Determines if String is only comprised of punctuation characters (any character other than the letters or numbers).

			@param source: String to check.
			@param allowSpaces: Indicates to count spaces as punctuation <code>true</code>, or not to <code>false</code>.
			@return Returns <code>true</code> if String is only punctuation; otherwise <code>false</code>.
		*/
		public static function isPunctuation(source:String, allowSpaces:Boolean = true):Boolean {
			if (StringUtil.getNumbersFromString(source).length != 0 || StringUtil.getLettersFromString(source).length != 0)
				return false;

			if (!allowSpaces)
				return source.split(' ').length == 1;

			return true;
		}

		/**
			Determines if String is only comprised of upper case letters.

			@param source: String to check.
			@return Returns <code>true</code> if String is only upper case characters; otherwise <code>false</code>.
			@usageNote This function counts numbers, spaces, punctuation and special characters as upper case.
		*/
		public static function isUpperCase(source:String):Boolean {
			if (source != source.toUpperCase())
				return false;

			return true;
		}

		/**
			Determines if String is only comprised of lower case letters.

			@param source: String to check.
			@return Returns <code>true</code> if String is only lower case characters; otherwise <code>false</code>.
			@usageNote This function counts numbers, spaces, punctuation and special characters as lower case.
		*/
		public static function isLowerCase(source:String):Boolean {
			if (source != source.toLowerCase())
				return false;

			return true;
		}

		/**
			Determines if String is only comprised of numbers.

			@param source: String to check.
			@return Returns <code>true</code> if String is a number; otherwise <code>false</code>.
		*/
		public static function isNumber(source:String):Boolean {
			var trimmed:String = StringUtil.trim(source);

			if (trimmed.length < source.length || source.length == 0)
				return false;

			return !isNaN(Number(source));
		}

		/**
			Searches the String for an occurrence of an upper case letter.

			@param source: String to search for a upper case letter.
			@return The index of the first occurrence of a upper case letter or <code>-1</code>.
		*/
		public static function indexOfUpperCase(source:String, startIndex:uint = 0):int {
			var letters:Array = source.split('');
			var i:int         = startIndex - 1;

			while (++i < letters.length)
				if (letters[i] == letters[i].toUpperCase() && letters[i] != letters[i].toLowerCase())
					return i;

			return -1;
		}

		/**
			Searches the String for an occurrence of a lower case letter.

			@param source: String to search for a lower case letter.
			@return The index of the first occurrence of a lower case letter or <code>-1</code>.
		*/
		public static function indexOfLowerCase(source:String, startIndex:uint = 0):int {
			var letters:Array = source.split('');
			var i:int         = startIndex - 1;

			while (++i < letters.length)
				if (letters[i] == letters[i].toLowerCase() && letters[i] != letters[i].toUpperCase())
					return i;

			return -1;
		}

		/**
			Returns all the numeric characters from a String.

			@param source: String to return numbers from.
			@return String containing only numbers.
		*/
		public static function getNumbersFromString(source:String):String {
			var pattern:RegExp = /[^0-9]/g;
			return source.replace(pattern, '');
		}

		/**
			Returns all the letter characters from a String.

			@param source: String to return letters from.
			@return String containing only letters.
		*/
		public static function getLettersFromString(source:String):String {
			var pattern:RegExp = /[[:digit:]|[:punct:]|\s]/g;
			return source.replace(pattern, '');
		}

		/**
			Determines if String contains search String.

			@param source: String to search in.
			@param search: String to search for.
			@return Returns the frequency of the search term found in source String.
		*/
		public static function contains(source:String, search:String):uint {
			var pattern:RegExp = new RegExp(search, 'g');
			return source.match(pattern).length;
		}

		/**
			Strips whitespace (or other characters) from the beginning of a String.

			@param source: String to remove characters from.
			@param removeChars: Characters to strip (case sensitive). Defaults to whitespace characters.
			@return String with characters removed.
		*/
		public static function trimLeft(source:String, removeChars:String = StringUtil.WHITESPACE):String {
			var pattern:RegExp = new RegExp('^[' + removeChars + ']+', '');
			return source.replace(pattern, '');
		}

		/**
			Strips whitespace (or other characters) from the end of a String.

			@param source: String to remove characters from.
			@param removeChars: Characters to strip (case sensitive). Defaults to whitespace characters.
			@return String with characters removed.
		*/
		public static function trimRight(source:String, removeChars:String = StringUtil.WHITESPACE):String {
			var pattern:RegExp = new RegExp('[' + removeChars + ']+$', '');
			return source.replace(pattern, '');
		}

		/**
			Strips whitespace (or other characters) from the beginning and end of a String.

			@param source: String to remove characters from.
			@param removeChars: Characters to strip (case sensitive). Defaults to whitespace characters.
			@return String with characters removed.
		*/
		public static function trim(source:String, removeChars:String = StringUtil.WHITESPACE):String {
			var pattern:RegExp = new RegExp('^[' + removeChars + ']+|[' + removeChars + ']+$', 'g');
			return source.replace(pattern, '');
		}

		/**
			Removes additional spaces from String.

			@param source: String to remove extra spaces from.
			@return String with additional spaces removed.
		*/
		public static function removeExtraSpaces(source:String):String {
			var pattern:RegExp = /( )+/g;
			return StringUtil.trim(source.replace(pattern, ' '), ' ');
		}

		/**
			Removes tabs, linefeeds, carriage returns and spaces from String.

			@param source: String to remove whitespace from.
			@return String with whitespace removed.
		*/
		public static function removeWhitespace(source:String):String {
			var pattern:RegExp = new RegExp('[' + StringUtil.WHITESPACE + ']', 'g');
			return source.replace(pattern, '');
		}

		/**
			Removes characters from a source String.

			@param source: String to remove characters from.
			@param remove: String describing characters to remove.
			@return String with characters removed.
		*/
		public static function remove(source:String, remove:String):String {
			return StringUtil.replace(source, remove, '');
		}

		/**
			用新字符替换目标字符。
			@param source: 用于替换字符的字符串。
			@param remove: 描述要删除的字符的字符串。
			@param replace: 用于替换已删除字符的字符串。
			@return 已替换字符串。
		*/
		public static function replace(source:String, remove:String, replace:String):String {
			return source.split(remove).join(replace);
		}

		/**
			Removes a character at a specific index.

			@param source: String to remove character from.
			@param position: Position of character to remove.
			@return String with character removed.
		*/
		public static function removeAt(source:String, position:int):String {
			return StringUtil.replaceAt(source, position, '');
		}

		/**
			Replaces a character at a specific index with new characters.

			@param source: String to replace characters from.
			@param position: Position of character to replace.
			@param replace: String to replace removed character.
			@return String with character replaced.
		*/
		public static function replaceAt(source:String, position:int, replace:String):String {
			var parts:Array = source.split('');
			parts.splice(position, 1, replace);
			return parts.join('');
		}

		/**
			Adds characters at a specific index.

			@param source: String to add characters to.
			@param position: Position in which to add characters.
			@param addition: String to add.
			@return String with characters added.
		*/
		public static function addAt(source:String, position:int, addition:String):String {
			var parts:Array = source.split('');
			parts.splice(position, 0, addition);
			return parts.join('');
		}

		/**
			Counts the number of words in a String.

			@param source: String in which to count words.
			@return The amount of words.
		*/
		public static function getWordCount(source:String):uint {
			return StringUtil.removeExtraSpaces(StringUtil.trim(source)).split(' ').length;
		}

		/**
			Extracts all the unique characters from a source String.

			@param source: String to find unique characters within.
			@return String containing unique characters from source String.
		*/
		public static function getUniqueCharacters(source:String):String {
			var unique:String = '';
			var i:uint        = 0;
			var char:String;

			while (i < source.length) {
				char = source.charAt(i);

				if (unique.indexOf(char) == -1)
					unique += char;

				i++;
			}

			return unique;
		}
	}
}